Skip to main content

Uniswap V3 Liquidity

Uniswap V3 Liquidity Examples

Examples of minting new position, collect fees, increase and decrease liquidity.

创建新头寸、收取费用、增加和减少流动性的示例。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint tokenId,
bytes calldata data
) external returns (bytes4);
}

contract UniswapV3Liquidity is IERC721Receiver {
IERC20 private constant dai = IERC20(DAI);
IWETH private constant weth = IWETH(WETH);

int24 private constant MIN_TICK = -887272;
int24 private constant MAX_TICK = -MIN_TICK;
int24 private constant TICK_SPACING = 60;

INonfungiblePositionManager public nonfungiblePositionManager =
INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88);

function onERC721Received(
address operator,
address from,
uint tokenId,
bytes calldata
) external returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}

function mintNewPosition(
uint amount0ToAdd,
uint amount1ToAdd
) external returns (uint tokenId, uint128 liquidity, uint amount0, uint amount1) {
dai.transferFrom(msg.sender, address(this), amount0ToAdd);
weth.transferFrom(msg.sender, address(this), amount1ToAdd);

dai.approve(address(nonfungiblePositionManager), amount0ToAdd);
weth.approve(address(nonfungiblePositionManager), amount1ToAdd);

INonfungiblePositionManager.MintParams
memory params = INonfungiblePositionManager.MintParams({
token0: DAI,
token1: WETH,
fee: 3000,
tickLower: (MIN_TICK / TICK_SPACING) * TICK_SPACING,
tickUpper: (MAX_TICK / TICK_SPACING) * TICK_SPACING,
amount0Desired: amount0ToAdd,
amount1Desired: amount1ToAdd,
amount0Min: 0,
amount1Min: 0,
recipient: address(this),
deadline: block.timestamp
});

(tokenId, liquidity, amount0, amount1) = nonfungiblePositionManager.mint(
params
);

if (amount0 < amount0ToAdd) {
dai.approve(address(nonfungiblePositionManager), 0);
uint refund0 = amount0ToAdd - amount0;
dai.transfer(msg.sender, refund0);
}
if (amount1 < amount1ToAdd) {
weth.approve(address(nonfungiblePositionManager), 0);
uint refund1 = amount1ToAdd - amount1;
weth.transfer(msg.sender, refund1);
}
}

function collectAllFees(
uint tokenId
) external returns (uint amount0, uint amount1) {
INonfungiblePositionManager.CollectParams
memory params = INonfungiblePositionManager.CollectParams({
tokenId: tokenId,
recipient: address(this),
amount0Max: type(uint128).max,
amount1Max: type(uint128).max
});

(amount0, amount1) = nonfungiblePositionManager.collect(params);
}

function increaseLiquidityCurrentRange(
uint tokenId,
uint amount0ToAdd,
uint amount1ToAdd
) external returns (uint128 liquidity, uint amount0, uint amount1) {
dai.transferFrom(msg.sender, address(this), amount0ToAdd);
weth.transferFrom(msg.sender, address(this), amount1ToAdd);

dai.approve(address(nonfungiblePositionManager), amount0ToAdd);
weth.approve(address(nonfungiblePositionManager), amount1ToAdd);

INonfungiblePositionManager.IncreaseLiquidityParams
memory params = INonfungiblePositionManager.IncreaseLiquidityParams({
tokenId: tokenId,
amount0Desired: amount0ToAdd,
amount1Desired: amount1ToAdd,
amount0Min: 0,
amount1Min: 0,
deadline: block.timestamp
});

(liquidity, amount0, amount1) = nonfungiblePositionManager.increaseLiquidity(
params
);
}

function decreaseLiquidityCurrentRange(
uint tokenId,
uint128 liquidity
) external returns (uint amount0, uint amount1) {
INonfungiblePositionManager.DecreaseLiquidityParams
memory params = INonfungiblePositionManager.DecreaseLiquidityParams({
tokenId: tokenId,
liquidity: liquidity,
amount0Min: 0,
amount1Min: 0,
deadline: block.timestamp
});

(amount0, amount1) = nonfungiblePositionManager.decreaseLiquidity(params);
}
}

interface INonfungiblePositionManager {
struct MintParams {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint amount0Desired;
uint amount1Desired;
uint amount0Min;
uint amount1Min;
address recipient;
uint deadline;
}

function mint(
MintParams calldata params
)
external
payable
returns (uint tokenId, uint128 liquidity, uint amount0, uint amount1);

struct IncreaseLiquidityParams {
uint tokenId;
uint amount0Desired;
uint amount1Desired;
uint amount0Min;
uint amount1Min;
uint deadline;
}

function increaseLiquidity(
IncreaseLiquidityParams calldata params
) external payable returns (uint128 liquidity, uint amount0, uint amount1);

struct DecreaseLiquidityParams {
uint tokenId;
uint128 liquidity;
uint amount0Min;
uint amount1Min;
uint deadline;
}

function decreaseLiquidity(
DecreaseLiquidityParams calldata params
) external payable returns (uint amount0, uint amount1);

struct CollectParams {
uint tokenId;
address recipient;
uint128 amount0Max;
uint128 amount1Max;
}

function collect(
CollectParams calldata params
) external payable returns (uint amount0, uint amount1);
}

interface IERC20 {
function totalSupply() external view returns (uint);

function balanceOf(address account) external view returns (uint);

function transfer(address recipient, uint amount) external returns (bool);

function allowance(address owner, address spender) external view returns (uint);

function approve(address spender, uint amount) external returns (bool);

function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);

event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
}

interface IWETH is IERC20 {
function deposit() external payable;

function withdraw(uint amount) external;
}

Test with Foundry

与 Foundry 进行测试

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint tokenId,
bytes calldata data
) external returns (bytes4);
}

contract UniswapV3Liquidity is IERC721Receiver {
IERC20 private constant dai = IERC20(DAI);
IWETH private constant weth = IWETH(WETH);

int24 private constant MIN_TICK = -887272;
int24 private constant MAX_TICK = -MIN_TICK;
int24 private constant TICK_SPACING = 60;

INonfungiblePositionManager public nonfungiblePositionManager =
INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88);

function onERC721Received(
address operator,
address from,
uint tokenId,
bytes calldata
) external returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}

function mintNewPosition(
uint amount0ToAdd,
uint amount1ToAdd
) external returns (uint tokenId, uint128 liquidity, uint amount0, uint amount1) {
dai.transferFrom(msg.sender, address(this), amount0ToAdd);
weth.transferFrom(msg.sender, address(this), amount1ToAdd);

dai.approve(address(nonfungiblePositionManager), amount0ToAdd);
weth.approve(address(nonfungiblePositionManager), amount1ToAdd);

INonfungiblePositionManager.MintParams
memory params = INonfungiblePositionManager.MintParams({
token0: DAI,
token1: WETH,
fee: 3000,
tickLower: (MIN_TICK / TICK_SPACING) * TICK_SPACING,
tickUpper: (MAX_TICK / TICK_SPACING) * TICK_SPACING,
amount0Desired: amount0ToAdd,
amount1Desired: amount1ToAdd,
amount0Min: 0,
amount1Min: 0,
recipient: address(this),
deadline: block.timestamp
});

(tokenId, liquidity, amount0, amount1) = nonfungiblePositionManager.mint(
params
);

if (amount0 < amount0ToAdd) {
dai.approve(address(nonfungiblePositionManager), 0);
uint refund0 = amount0ToAdd - amount0;
dai.transfer(msg.sender, refund0);
}
if (amount1 < amount1ToAdd) {
weth.approve(address(nonfungiblePositionManager), 0);
uint refund1 = amount1ToAdd - amount1;
weth.transfer(msg.sender, refund1);
}
}

function collectAllFees(
uint tokenId
) external returns (uint amount0, uint amount1) {
INonfungiblePositionManager.CollectParams
memory params = INonfungiblePositionManager.CollectParams({
tokenId: tokenId,
recipient: address(this),
amount0Max: type(uint128).max,
amount1Max: type(uint128).max
});

(amount0, amount1) = nonfungiblePositionManager.collect(params);
}

function increaseLiquidityCurrentRange(
uint tokenId,
uint amount0ToAdd,
uint amount1ToAdd
) external returns (uint128 liquidity, uint amount0, uint amount1) {
dai.transferFrom(msg.sender, address(this), amount0ToAdd);
weth.transferFrom(msg.sender, address(this), amount1ToAdd);

dai.approve(address(nonfungiblePositionManager), amount0ToAdd);
weth.approve(address(nonfungiblePositionManager), amount1ToAdd);

INonfungiblePositionManager.IncreaseLiquidityParams
memory params = INonfungiblePositionManager.IncreaseLiquidityParams({
tokenId: tokenId,
amount0Desired: amount0ToAdd,
amount1Desired: amount1ToAdd,
amount0Min: 0,
amount1Min: 0,
deadline: block.timestamp
});

(liquidity, amount0, amount1) = nonfungiblePositionManager.increaseLiquidity(
params
);
}

function decreaseLiquidityCurrentRange(
uint tokenId,
uint128 liquidity
) external returns (uint amount0, uint amount1) {
INonfungiblePositionManager.DecreaseLiquidityParams
memory params = INonfungiblePositionManager.DecreaseLiquidityParams({
tokenId: tokenId,
liquidity: liquidity,
amount0Min: 0,
amount1Min: 0,
deadline: block.timestamp
});

(amount0, amount1) = nonfungiblePositionManager.decreaseLiquidity(params);
}
}

interface INonfungiblePositionManager {
struct MintParams {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint amount0Desired;
uint amount1Desired;
uint amount0Min;
uint amount1Min;
address recipient;
uint deadline;
}

function mint(
MintParams calldata params
)
external
payable
returns (uint tokenId, uint128 liquidity, uint amount0, uint amount1);

struct IncreaseLiquidityParams {
uint tokenId;
uint amount0Desired;
uint amount1Desired;
uint amount0Min;
uint amount1Min;
uint deadline;
}

function increaseLiquidity(
IncreaseLiquidityParams calldata params
) external payable returns (uint128 liquidity, uint amount0, uint amount1);

struct DecreaseLiquidityParams {
uint tokenId;
uint128 liquidity;
uint amount0Min;
uint amount1Min;
uint deadline;
}

function decreaseLiquidity(
DecreaseLiquidityParams calldata params
) external payable returns (uint amount0, uint amount1);

struct CollectParams {
uint tokenId;
address recipient;
uint128 amount0Max;
uint128 amount1Max;
}

function collect(
CollectParams calldata params
) external payable returns (uint amount0, uint amount1);
}

interface IERC20 {
function totalSupply() external view returns (uint);

function balanceOf(address account) external view returns (uint);

function transfer(address recipient, uint amount) external returns (bool);

function allowance(address owner, address spender) external view returns (uint);

function approve(address spender, uint amount) external returns (bool);

function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);

event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
}

interface IWETH is IERC20 {
function deposit() external payable;

function withdraw(uint amount) external;
}